import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;




public class CellularAutomata {

	static Integer highestHighestFit = 0;
	static RuleSet bestRule=null;
	Properties properties;
	private static final int GOL_GENERATIONS=100;
	public static void main (String args[]) 
	{
		String file;
		if(args.length > 1)
		{
			file = args[1];
		}
		else
		{
			file = new String("cd_ga.properties");
		}
		
		int xSize = 50;
		int ySize = 50;
		DisplayGrid seedGrid = new DisplayGrid(ySize,xSize,"Seed");
		SeedPopulation seed = new SeedPopulation(xSize,ySize, seedGrid,true);
		DisplayGrid goalGrid = new DisplayGrid(ySize,xSize,"Goal");
		GoalPopulation goal  = new GoalPopulation(ySize,xSize,goalGrid);
		//DisplayGrid grid = new DisplayGrid(ySize,xSize,"Evolved");
		EvolvedPopulation evolved= new EvolvedPopulation(seed,null);
		List<Pair<Integer,RuleSet>> results = doFirstGeneration(50,evolved,seed,goal);
		System.out.println("First generation done");
		Set<RuleSet> currentResults;
		for(int i=0; i<1000; i++)
		{
			currentResults = runGA(results);			
			results = doGeneration(currentResults,evolved,seed,goal);
			System.out.println("Completed GA number: " + i);
		}
		System.out.println("Final best of the best fit: " + highestHighestFit);
		DisplayGrid finalGrid = new DisplayGrid(ySize,xSize,"Final");
		EvolvedPopulation finalEvolved = new EvolvedPopulation(seed,finalGrid);
		GameOfLife gol = new GameOfLife();
		gol.reset(finalEvolved, bestRule);
		gol.runSimulation(GOL_GENERATIONS);
	}
	
	public static Set<RuleSet> runGA(List<Pair<Integer,RuleSet>> results)
	{
		List<RuleSet> highFit = binaryTournament(results,20);
		assert(highFit.size() == results.size());
		Set<RuleSet> mutated = mutate(highFit);
		return mutated;
	}
	
	public static Set<RuleSet> mutate(List<RuleSet> initialRules)
	{
		Set<RuleSet> rules = new HashSet<RuleSet>();
		System.out.println("Rules size: " + initialRules.size());
		for(RuleSet rule:initialRules)
		{
			rule.mutate(0.1f);
			rules.add(rule);
		}
		return rules;
	}
	
	public static List<RuleSet> binaryTournament(List<Pair<Integer,RuleSet>> results,int tournamentSize)
	{
		
		List<RuleSet> highFit = new Vector<RuleSet>();
		//Select a new set of results of the same size from the current set
		//by selecting 20 at random and performing a Binary Tournament selection
		//until you reach a new set with the highest Fit rules
		Random rand = new Random(Math.abs(System.nanoTime()));
		int size = results.size();
		while(highFit.size() < size)
		{
			Integer bestSoFar = 0;
			Integer indexOfBestSoFar = 0;
			Integer secondBest = 0;
			Integer indexOfSecondBest = 0;
			for(int j=0;j<tournamentSize; j++)
			{	
				Integer indexToCheck = rand.nextInt(size);
				Integer current= results.get(indexToCheck).first;
				if(current > bestSoFar)
				{
					secondBest = bestSoFar;
					indexOfSecondBest=indexOfBestSoFar;
					bestSoFar = current;
					indexOfBestSoFar = indexToCheck;
				}
				else if(current > secondBest)
				{
					secondBest = current;
					indexOfSecondBest = indexToCheck;
				}
			}
			if(bestSoFar > highestHighestFit)
			{
				highestHighestFit = bestSoFar;
				System.out.println("Better value found! " + highestHighestFit);
				bestRule = results.get(indexOfBestSoFar).second.copy();
			}
			RuleSet newRule1 = results.get(indexOfBestSoFar).second.copy();
			RuleSet newRule2 = results.get(indexOfSecondBest).second.copy();
			newRule1.crossover(newRule2);
			highFit.add(newRule1);
			highFit.add(newRule2);
		}
		return highFit;
	}
	public static List<Pair<Integer,RuleSet>> doFirstGeneration(int numberOfRules,EvolvedPopulation evolved, SeedPopulation seed, 
			GoalPopulation goal)
	{
		RuleSet rules = new RuleSet();
		
		GameOfLife gol = new GameOfLife(evolved, rules);

		List<Pair<Integer,RuleSet>> comparedRules = new Vector<Pair<Integer,RuleSet>>();
		int i=0;
		while(i<numberOfRules)
		{	
			System.out.println("Calculating rule: " + i);
			long start = System.currentTimeMillis();	
			gol.runSimulation(GOL_GENERATIONS);
			System.out.println("One run took: " +(System.currentTimeMillis() - start));			
			comparedRules.add(new Pair<Integer,RuleSet>(goal.compare(evolved), rules));
			i++;
			rules = new RuleSet();
			evolved.reset(seed);
			gol.reset(evolved,rules);
			System.out.println("Completed calculating rule: " + i);
		}
		return comparedRules;
	}
	
	public static List<Pair<Integer,RuleSet>> doGeneration(Set<RuleSet> rulesSet,
			EvolvedPopulation evolved, SeedPopulation seed, GoalPopulation goal)
	{
		Iterator<RuleSet> iter = rulesSet.iterator();
		GameOfLife gol = new GameOfLife();
		List<Pair<Integer,RuleSet>> comparedRules = new Vector<Pair<Integer,RuleSet>>();
		evolved.reset(seed);
		long start = 0;
		while(iter.hasNext())
		{
			start = System.currentTimeMillis();
			RuleSet rules = iter.next();
			gol.reset(evolved, rules);
			
			gol.runSimulation(GOL_GENERATIONS);
			comparedRules.add(new Pair<Integer,RuleSet>(goal.compare(evolved), rules));
			evolved.reset(seed);
			gol.reset(evolved,rules);
			//System.out.println("One run took: " +(System.currentTimeMillis() - start));	
		}
		return comparedRules;
	}
	public void loadProperties(String fileName)
	{
		this.properties = new Properties() ;
		URL url =  ClassLoader.getSystemResource(fileName);
		try{
			properties.load(new FileInputStream(new File(url.getFile())));
		}catch(IOException e)
		{
			System.out.println("Could not load properties file");
		}
	}
}
